import * as THREE from "three";
import Stats from "three/examples/jsm/libs/stats.module";

/**
 * 初始化相机
 * @param initPosition 初始化位置
 */
export function initCamera(initPosition: { x: number, y: number, z: number } | undefined): THREE.Camera {
    const position = initPosition === undefined ? new THREE.Vector3(-30, 40, 30) : initPosition;
    const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

    const { x, y, z } = position;
    camera.position.set(x, y, z );
    camera.lookAt(0, 0, 0);

    return camera;
}

/**
 * 初始化渲染器
 * @param properties 初始化参数
 */
export function initWebGLRenderer(properties: any): THREE.WebGLRenderer {
    const props = (typeof properties !== "undefined" && properties) ? properties : {};
    const renderer = new THREE.WebGLRenderer(props);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setClearColor(0x000000);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;

    return renderer;
}

/**
 * 性能统计
 * @param mode 模式  -  0 显示：渲染帧率  刷新频率,一秒渲染次数  1 显示：渲染周期 渲染一帧多长时间(单位：毫秒ms)
 */
export function initStat(mode: number | undefined): Stats {
    const stat = Stats();
    stat.setMode( (typeof mode === "undefined") ? 0 : mode);
    return stat;
}

/**
 * 初始化默认光源
 * @param scene 场景
 * @param initPosition 初始化位置
 */
export function initDefaultLighting(scene, initPosition) {
    const position = initPosition ? initPosition : new THREE.Vector3(-10, 30, 40);

    const spotLight = new THREE.SpotLight(0xffffff);
    spotLight.position.copy(position);
    spotLight.shadow.mapSize.width = window.innerWidth;
    spotLight.shadow.mapSize.height = window.innerHeight;
    spotLight.shadow.camera.fov = 15;
    spotLight.castShadow = true;
    spotLight.decay = 2;
    spotLight.penumbra = 0.05;
    spotLight.name = "spotLight"

    scene.add(spotLight);

    const ambientLight = new THREE.AmbientLight(0x343434);
    ambientLight.name = "ambientLight";

    scene.add(ambientLight);
}

/**
 * 添加地面
 * @param scene 场景
 * @param useTexture 是否使用纹理
 */
export function addLargeGroundPlane(scene: THREE.Scene, useTexture: boolean) {
    const planeGeometry = new THREE.PlaneGeometry(10000, 10000);
    const planeMaterial = new THREE.MeshPhongMaterial({
        color: 0xffffff,
    });
    if (useTexture) {
        planeMaterial.map = new THREE.TextureLoader().load(new URL("@/assets/textures/general/floor-wood.jpg", import.meta.url).href);
        planeMaterial.map.wrapS = THREE.RepeatWrapping;
        planeMaterial.map.wrapT = THREE.RepeatWrapping;
        planeMaterial.map.repeat.set(80,80);
    }
    const plane = new THREE.Mesh(planeGeometry, planeMaterial);
    plane.receiveShadow = true;
    plane.rotateX(-0.5 * Math.PI);
    plane.position.set(0, -30, 0);
    scene.add(plane);

    return plane;
}

/**
 * 设置随机颜色
 * @param object
 * @param scale
 */
export function setRandomColors(object, scale) {
    const children = object.children;
    if (children && children.length > 0) {
        children.forEach(function (e) {
            setRandomColors(e, scale)
        });
    } else {
        // no children assume contains a mesh
        if (object instanceof THREE.Mesh) {
            if (object.material instanceof Array) {
                object.material.forEach(function(m) {
                    m.color = new THREE.Color(scale(Math.random()).hex());
                    if (m.name.indexOf("building") == 0) {
                        m.emissive = new THREE.Color(0x444444);
                        m.transparent = true;
                        m.opacity = 0.8;
                    }
                });
            } else {
                object.material.color = new THREE.Color(scale(Math.random()).hex());
                if (object.material.name.indexOf("building") == 0) {
                    object.material.emissive = new THREE.Color(0x444444);
                    object.material.transparent = true;
                    object.material.opacity = 0.8;
                }
            }
        }
    }
}

/**
 * 使用 MeshNormalMaterial
 * @param geometry
 * @param material
 */
export function applyMeshNormalMaterial(geometry, material) {
    if (!material || material.type !== "MeshNormalMaterial")  {
        material = new THREE.MeshNormalMaterial();
        material.side = THREE.DoubleSide;
    }
    return new THREE.Mesh(geometry, material)
}

/**
 * 使用 MeshStandardMaterial
 * @param geometry geometry
 * @param material material
 */
export function applyMeshStandardMaterial(geometry, material) {
    if (!material || material.type !== "MeshStandardMaterial")  {
        material = new THREE.MeshStandardMaterial({color: 0xff0000})
        material.side = THREE.DoubleSide;
    }
    return new THREE.Mesh(geometry, material)
}

/**
 * canvas 创建 纹理
 */
export function createGhostTexture() {
    var canvas = document.createElement('canvas');
    canvas.width = 32;
    canvas.height = 32;

    var ctx = canvas.getContext('2d');
    // the body
    ctx.translate(-81, -84);

    ctx.fillStyle = "orange";
    ctx.beginPath();
    ctx.moveTo(83, 116);
    ctx.lineTo(83, 102);
    ctx.bezierCurveTo(83, 94, 89, 88, 97, 88);
    ctx.bezierCurveTo(105, 88, 111, 94, 111, 102);
    ctx.lineTo(111, 116);
    ctx.lineTo(106.333, 111.333);
    ctx.lineTo(101.666, 116);
    ctx.lineTo(97, 111.333);
    ctx.lineTo(92.333, 116);
    ctx.lineTo(87.666, 111.333);
    ctx.lineTo(83, 116);
    ctx.fill();

    // the eyes
    ctx.fillStyle = "white";
    ctx.beginPath();
    ctx.moveTo(91, 96);
    ctx.bezierCurveTo(88, 96, 87, 99, 87, 101);
    ctx.bezierCurveTo(87, 103, 88, 106, 91, 106);
    ctx.bezierCurveTo(94, 106, 95, 103, 95, 101);
    ctx.bezierCurveTo(95, 99, 94, 96, 91, 96);
    ctx.moveTo(103, 96);
    ctx.bezierCurveTo(100, 96, 99, 99, 99, 101);
    ctx.bezierCurveTo(99, 103, 100, 106, 103, 106);
    ctx.bezierCurveTo(106, 106, 107, 103, 107, 101);
    ctx.bezierCurveTo(107, 99, 106, 96, 103, 96);
    ctx.fill();

    // the pupils
    ctx.fillStyle = "blue";
    ctx.beginPath();
    ctx.arc(101, 102, 2, 0, Math.PI * 2, true);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(89, 102, 2, 0, Math.PI * 2, true);
    ctx.fill();


    var texture = new THREE.Texture(canvas);
    texture.needsUpdate = true;
    return texture;
}

/**
 * 重新绘制几何体并更新UI页面
 * @param gui
 * @param scene
 * @param controls
 * @param geomFunction
 */
export function redrawGeometryAndUpdateUI(gui, scene, controls, geomFunction) {
    guiRemoveFolder(gui, controls.specificMaterialFolder);
    guiRemoveFolder(gui, controls.currentMaterialFolder);
    if (controls.mesh) {
        scene.remove(controls.mesh);
    }
    const changeMat = eval("(" + controls.appliedMaterial + ")")
    if (controls.mesh) {
        controls.mesh = changeMat(geomFunction(), controls.mesh.material);
    } else {
        controls.mesh = changeMat(geomFunction());
    }

    controls.mesh.castShadow = controls.castShadow;
    scene.add(controls.mesh)
    controls.currentMaterialFolder = addBasicMaterialSettings(gui, controls, controls.mesh.material, undefined);
    controls.specificMaterialFolder = addSpecificMaterialSettings(gui, controls, controls.mesh.material, undefined);
}

/**
 * 增加 基础的材质设置
 * @param gui
 * @param controls
 * @param material
 * @param name
 */
export function addBasicMaterialSettings(gui, controls, material, name) {

    const folderName = (name !== undefined) ? name : 'THREE.Material';

    controls.material = material;

    const folder = gui.addFolder(folderName);
    folder.add(controls.material, 'id');
    folder.add(controls.material, 'uuid');
    folder.add(controls.material, 'name');
    folder.add(controls.material, 'opacity', 0, 1, 0.01);
    folder.add(controls.material, 'transparent');
    folder.add(controls.material, 'overdraw', 0, 1, 0.01);
    folder.add(controls.material, 'visible');
    folder.add(controls.material, 'side', {FrontSide: 0, BackSide: 1, BothSides: 2}).onChange(function (side) {
        controls.material.side = parseInt(side)
    });

    folder.add(controls.material, 'colorWrite');
    folder.add(controls.material, 'flatShading').onChange(function(shading) {
        controls.material.flatShading = shading;
        controls.material.needsUpdate = true;
    });
    folder.add(controls.material, 'premultipliedAlpha');
    folder.add(controls.material, 'dithering');
    folder.add(controls.material, 'shadowSide', {FrontSide: 0, BackSide: 1, BothSides: 2});
    folder.add(controls.material, 'vertexColors', {NoColors: 0, FaceColors: 1, VertexColors: 2})
        .onChange((vertexColors) => {
        material.vertexColors = parseInt(vertexColors);
    });
    folder.add(controls.material, 'fog');

    return folder;
}

/**
 * 特殊材质设置
 * @param gui
 * @param controls
 * @param material
 * @param name
 */
export function addSpecificMaterialSettings(gui, controls, material, name) {
    controls.material = material;

    const folderName = (name !== undefined) ? name : 'THREE.' + material.type;
    const folder = gui.addFolder(folderName);

    switch (material.type) {
        case "MeshNormalMaterial":
            folder.add(controls.material,'展示线框');
            return folder;
        case "MeshPhongMaterial":
            controls.specular = material.specular.getStyle();
            folder.addColor(controls, '高光颜色').onChange( (e) => {
                material.specular.setStyle(e)
            });
            folder.add(material, '高光部分的亮度', 0, 100, 0.01);
            folder.add(controls.material,'展示线框');
            return folder;
        case "MeshStandardMaterial":
            controls.color = material.color.getStyle();
            folder.addColor(controls, '颜色').onChange( (e) => {
                material.color.setStyle(e)
            });
            controls.emissive = material.emissive.getStyle();
            folder.addColor(controls, '自发光').onChange( (e) => {
                material.emissive.setStyle(e)
            });
            // 表示材质像金属的程度, 非金属材料,如木材或石材,使用0.0,金属使用1.0
            folder.add(material, '金属质感', 0, 1, 0.01);
            // 表示模型表面的光滑或者说粗糙程度，越光滑镜面反射能力越强，越粗糙，表面镜面反射能力越弱，更多地表现为漫反射，0.0表示平滑的镜面反射,1.0表示完全漫反射。
            folder.add(material, '粗糙度', 0, 1, 0.01);
            folder.add(material, '展示线框');

            return folder;
    }
}

/**
 * Remove a folder from the dat.gui
 *
 * @param {*} gui
 * @param {*} folder
 */
function guiRemoveFolder(gui, folder) {
    if (folder && folder.name && gui.__folders[folder.name]) {
        gui.__folders[folder.name].close();
        gui.__folders[folder.name].domElement.parentNode.parentNode.removeChild(gui.__folders[folder.name].domElement.parentNode);
        delete gui.__folders[folder.name];
        gui.onResize();
    }
}

/**
 * 添加树和房子
 * @param scene 场景
 */
export function addHouseAndTree(scene) {

    createBoundingWall();
    createGroundPlane();
    createHouse();
    createTree();

    function createBoundingWall() {
        // 墙体
        const wallLeft = new THREE.BoxGeometry(70, 2, 2);
        const wallRight = new THREE.BoxGeometry(70, 2, 2);
        const wallTop = new THREE.BoxGeometry(2, 2, 50);
        const wallBottom = new THREE.BoxGeometry(2, 2, 50);
        // 墙体材质
        const wallMaterial = new THREE.MeshLambertMaterial({
            color: 0xa0522d,
            side: THREE.DoubleSide,
        });
        // 墙体网格模型Mesh
        const wallLeftMesh = new THREE.Mesh(wallLeft, wallMaterial);
        const wallRightMesh = new THREE.Mesh(wallRight, wallMaterial);
        const wallTopMesh = new THREE.Mesh(wallTop, wallMaterial);
        const wallBottomMesh = new THREE.Mesh(wallBottom, wallMaterial);
        // 墙体position设置
        wallLeftMesh.position.set(15, 1, -25);
        wallRightMesh.position.set(15, 1, 25);
        wallTopMesh.position.set(-19, 1, 0);
        wallBottomMesh.position.set(49, 1, 0);

        scene.add(wallLeftMesh, wallRightMesh, wallTopMesh, wallBottomMesh);
    }

    function createGroundPlane() {
        // 地面
        const planeGeometry = new THREE.PlaneGeometry(70, 50);
        const planeMaterial = new THREE.MeshLambertMaterial({
            color: 0x9acd32,
            side: THREE.DoubleSide,
        });
        const plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.receiveShadow = true;
        plane.rotation.x = -0.5 * Math.PI;
        plane.position.set(15, 0, 0);
        scene.add(plane);
    }

    function createHouse() {
        // 房顶
        const roof = new THREE.ConeGeometry(5, 4);
        const roofMesh = new THREE.Mesh(roof, new THREE.MeshLambertMaterial({
            color: 0x8b7213,
        }));
        // 房子主体
        const house = new THREE.CylinderGeometry(5, 5, 6);
        const houseMesh = new THREE.Mesh(house, new THREE.MeshLambertMaterial({
            color: 0xffe4c4,
        }));

        roofMesh.position.set(25, 8, 0);
        houseMesh.position.set(25, 3, 0);

        roofMesh.receiveShadow = true;
        houseMesh.receiveShadow = true;
        roofMesh.castShadow = true;
        houseMesh.castShadow = true;

        scene.add(roofMesh, houseMesh);
    }

    function createTree() {
        // 树干
        const trunk = new THREE.BoxGeometry(1, 8, 1);
        const trunkMesh = new THREE.Mesh(trunk, new THREE.MeshLambertMaterial({
            color: 0x8b4513,
        }));
        // 树叶
        const leave = new THREE.SphereGeometry(4);
        const leaveMesh = new THREE.Mesh(leave, new THREE.MeshLambertMaterial({
            color: 0x00ff00,
        }));

        trunkMesh.position.set(-10, 4, 0);
        leaveMesh.position.set(-10, 12, 0);

        trunkMesh.receiveShadow = true;
        trunkMesh.castShadow = true;
        leaveMesh.receiveShadow = true;
        leaveMesh.castShadow = true;

        scene.add(trunkMesh, leaveMesh);
    }
}
